package goben

import (
	"database/sql"
	"errors"
	"math/rand"
	"reflect"
	"regexp"
	"strings"
	"time"

	"gitee.com/zhangycsz/goben/internal/json"
	_ "github.com/go-sql-driver/mysql"
)

// 数据库配置文件
type IDbConfig struct {
	DriverName        string                                                             //驱动名称
	Prefix            string                                                             //前缀
	Dsn               string                                                             //mysql的链接,不支持读写分离
	WDsn              string                                                             //写库的链接,支持读写分离
	RDsn              string                                                             //读库的链接,支持读写分离
	IsShowSelectSql   bool                                                               //是否显示sql语句,select
	IsShowOptionSql   bool                                                               //是否回调操作sql语句,install,update,delete
	SqlOptionCallBack func(option, talenName, sql string, datalist []interface{})        //回调函数,option:操作add,edit,del,sql:sql语句,datalist:数据每个sql的回调操作
	SqlErrorCallBack  func(sql string, datalist []interface{}, option string, err error) //sql错误回调函数,sql:sql语句,datalist:数据每个sql的回调操作,option:操作提示，err错误信息
}

// 设置全局变量
var DbConfig *IDbConfig

// 创建IDb
func NewDb() *IDb {
	if Db == nil {
		Db = &IDb{}
	}
	return Db
}

// 数据库操作对象
type IDb struct {
	IDbConf *IDbConfig
	IsDebug bool      //是否开启调试模式
	CDB     *sql.DB   //当前数据库链接对象
	RDb     []*sql.DB //读库
	WDb     []*sql.DB //写库
}

// 全局配置文件
var Db *IDb

// 打开数据库
func (ths *IDb) Open() (err error) {
	ths.IDbConf = DbConfig

	if ths.IDbConf.DriverName == "" {
		ths.IDbConf.DriverName = "mysql"
	}
	if ths.IDbConf.DriverName != "mysql" {
		return errors.New("不支持的数据库驱动")
	}

	if ths.IDbConf.RDsn == "" && ths.IDbConf.WDsn == "" {
		ths.CDB, err = sql.Open(ths.IDbConf.DriverName, ths.IDbConf.Dsn)
		return err
	}
	if ths.IDbConf.RDsn != "" {
		rdsn := strings.Split(ths.IDbConf.RDsn, ",")
		ths.RDb = make([]*sql.DB, len(ths.IDbConf.RDsn))
		for i := 0; i < len(rdsn); i++ {
			db, err := sql.Open(ths.IDbConf.DriverName, rdsn[i])
			if err != nil {
				continue
			}
			ths.RDb[i] = db
		}
	}
	if ths.IDbConf.WDsn != "" {
		rdsn := strings.Split(ths.IDbConf.WDsn, ",")
		ths.WDb = make([]*sql.DB, len(ths.IDbConf.WDsn))
		for i := 0; i < len(rdsn); i++ {
			db, err := sql.Open(ths.IDbConf.DriverName, rdsn[i])
			if err != nil {
				continue
			}
			ths.WDb[i] = db
		}
	}

	return err
}

// 关闭数据库
func (ths *IDb) Close() {
	if ths.CDB != nil {
		ths.CDB.Close()
	}
	for i := 0; i < len(ths.RDb); i++ {
		ths.RDb[i].Close()
	}
	for i := 0; i < len(ths.WDb); i++ {
		ths.WDb[i].Close()
	}
}

// 获取读的数据库链接对象
func (ths *IDb) GetRDB() (db *sql.DB) {

	if len(ths.RDb) == 0 {
		return ths.CDB
	}
	if len(ths.RDb) == 1 {
		return ths.RDb[0]
	}
	rand.New(rand.NewSource(time.Now().UnixNano()))
	index := rand.Intn(len(ths.RDb))
	return ths.RDb[index]
}

// 获取读的数据库链接对象
func (ths *IDb) GetWDB() (db *sql.DB) {
	if len(ths.WDb) == 0 {
		return ths.CDB
	}
	if len(ths.WDb) == 1 {
		return ths.WDb[0]
	}
	rand.New(rand.NewSource(time.Now().UnixNano()))
	index := rand.Intn(len(ths.WDb))
	return ths.WDb[index]
}

// 数据事务,通过NewTable().
type DataTransactions struct {
	Sql       string        //执行的sql
	QueryList []interface{} //参数列表
}

// 事务开始
func Transactions(queueList []DataTransactions) (err error) {
	db := NewDb().GetWDB()
	err = db.Ping()
	if err != nil {
		return
	}
	tx, err := db.Begin()
	if err != nil {
		return
	}
	// 事务开始
	err = transactions(tx, queueList)
	if err != nil {
		err = tx.Rollback()
		return err
	}
	//提交
	err = tx.Commit()
	if err != nil {
		return
	}
	return
}

func transactions(tx *sql.Tx, queueList []DataTransactions) (err error) {
	for _, v := range queueList {
		smt, err := tx.Prepare(v.Sql)
		if err != nil {
			break
		}
		_, err = tx.Exec(v.Sql, v.QueryList...)
		smt.Close()
		if err != nil {
			break
		}
	}
	return
}

// 得到表在的字段
func GetTableField(table interface{}) (result []byte, err error) {
	result = make([]byte, 0)
	db := NewDb().GetRDB()
	talbeName := NewDb().IDbConf.Prefix + GetTable(table)
	sqls := "SHOW COLUMNS  from " + talbeName
	err = db.Ping()
	if err != nil {
		return
	}
	smt, err := db.Prepare(sqls)
	if err != nil {
		return
	}
	defer smt.Close()
	rows, err := smt.Query()
	if err != nil {
		return
	}
	defer rows.Close()
	resultData, err := GetTableColumnsTypeRow(rows)
	if len(resultData) > 0 {
		result, err = json.Marshal(resultData)
		return result, err
	}
	return
}

// 获取表名
func GetTable(table interface{}) (tableName string) {
	t := reflect.TypeOf(table)
	switch t.Kind() {
	case reflect.String:
		tableName = table.(string)
	case reflect.Struct: //是结构体
		tableName = convertCamelToTableName(t.Name())
	case reflect.Ptr: //是结构体指针
		tableName = convertCamelToTableName(t.Elem().Name())
	default:
		panic("表名设置错误")
	}
	return tableName
}

// 将驼峰命名转换为下划线命名的辅助函数
func convertCamelToTableName(camelCase string) string {
	// 使用正则表达式将每个大写字母前添加下划线
	underscoreRegexp := regexp.MustCompile("([a-z])([A-Z])")
	return strings.ToLower(underscoreRegexp.ReplaceAllString(camelCase, "${1}_${2}"))
}

// 读取数据库数据，返回map集合
//total 显示总条，0:不限制，1，值为限制条数

// 有些表字段和go语言无法识别，需要手动转换一下表字段类型，这里写一个函数来做这件事
func GetTableColumnsTypeRow(rows *sql.Rows) (result []interface{}, err error) {
	result = make([]interface{}, 0)
	columnsType, err := rows.ColumnTypes()
	if err != nil {
		return
	}

	for rows.Next() {
		resultColumns := make(map[string]interface{}, 0)
		var values []interface{}
		for _, v := range columnsType {
			//fmt.Println("Kind=", v.ScanType().Kind(), "name=", v.Name(), "type=", v.ScanType().Name())
			switch v.ScanType().Name() {
			case "NullBool":
				var val sql.NullBool
				values = append(values, &val)
			case "NullByte":
				var val sql.NullByte
				values = append(values, &val)
			case "NullString", "RawBytes", "string":
				var val sql.NullString
				values = append(values, &val)
			case "NullInt16":
				var val sql.NullInt16
				values = append(values, &val)
			case "NullInt32":
				var val sql.NullInt32
				values = append(values, &val)
			case "NullInt64":
				var val sql.NullInt64
				values = append(values, &val)
			case "NullFloat64":
				var val sql.NullFloat64
				values = append(values, &val)
			case "NullTime":
				var val sql.NullTime
				values = append(values, &val)
			default:
				var val interface{}
				values = append(values, &val)
			}
		}

		err := rows.Scan(values...)
		if err != nil {
			break
		}

		for i, v := range values {
			//fmt.Println("TypeOf name =", columnsType[i].Name(), "type=", reflect.TypeOf(v).Elem().Name())
			switch vv := v.(type) {

			case *sql.NullBool:
				resultColumns[columnsType[i].Name()] = vv.Bool
			case *sql.NullByte:
				resultColumns[columnsType[i].Name()] = vv.Byte
			case *sql.NullString:
				resultColumns[columnsType[i].Name()] = vv.String
			case *sql.NullInt16:
				resultColumns[columnsType[i].Name()] = vv.Int16
			case *sql.NullInt32:
				resultColumns[columnsType[i].Name()] = vv.Int32
			case *sql.NullInt64:
				resultColumns[columnsType[i].Name()] = vv.Int64
			case *sql.NullFloat64:
				resultColumns[columnsType[i].Name()] = vv.Float64
			case *sql.NullTime:
				resultColumns[columnsType[i].Name()] = vv.Time

			default:
				resultColumns[columnsType[i].Name()] = vv
			}
		}
		result = append(result, resultColumns)

	}
	return
}
